from typing import List, Dict, Optional, Tuple
from enum import Enum
from dataclasses import dataclass
from datetime import datetime
import zmq
import time
from kline_cache import KlineCache

class OrderType(Enum):
    BUY = 0
    SELL = 1

@dataclass
class AccountInfo:
    account_number: int
    broker: str
    balance: float
    equity: float
    profit: float

@dataclass
class Position:
    ticket: int
    symbol: str
    type: int
    lots: float
    open_price: float
    sl: float
    tp: float
    profit: float
    magic: int
    commission: float  # 手续费
    swap: float       # 隔夜利息

@dataclass
class MarketInfo:
    symbol: str
    bid: float
    ask: float
    point: float
    lot_size: float

@dataclass
class Bar:
    time: datetime
    open: float
    high: float
    low: float
    close: float
    volume: float
    spread: int

class MT4Client:
    def __init__(self, host="localhost", port=5555, pub_port=5556):
        """初始化MT4客户端"""
        self.context = zmq.Context()
        self.socket = self.context.socket(zmq.REQ)
        self.socket.setsockopt(zmq.LINGER, 0)  # 设置linger为0，确保连接可以立即关闭
        self.socket.connect(f"tcp://{host}:{port}")
        self.socket.RCVTIMEO = 30000  # 30秒超时
        self.socket.SNDTIMEO = 30000  # 30秒超时
        
        # 创建订阅socket
        self.sub_socket = self.context.socket(zmq.SUB)
        self.sub_socket.connect(f"tcp://{host}:{pub_port}")
        self.sub_socket.setsockopt(zmq.SUBSCRIBE, b"")  # 订阅所有消息
        
        self.kline_cache = KlineCache()
        
    def _send_command(self, command: str) -> str:
        """发送命令到MT4并获取响应"""
        try:
            self.socket.send_string(command)
            response = self.socket.recv_string()
            return response
        except Exception as e:
            raise Exception(f"与MT4通信失败: {str(e)}")
            
    def get_account_info(self) -> AccountInfo:
        """获取账户信息"""
        response = self._send_command("ACCOUNT_INFO")
        parts = response.split("|")
        
        if parts[0] == "ACCOUNT_INFO":
            return AccountInfo(
                account_number=int(parts[1]),
                broker=parts[2],
                balance=float(parts[3]),
                equity=float(parts[4]),
                profit=float(parts[5])
            )
        else:
            raise Exception(f"获取账户信息失败: {response}")
        
    def get_positions(self) -> List[Position]:
        """获取当前持仓"""
        response = self._send_command("GET_POSITIONS")
        print(f"原始持仓数据: {response}")  # 打印原始响应数据
        parts = response.split("|")
        
        if parts[0] == "POSITIONS":
            positions = []
            positions_data = parts[1].split(";") if len(parts) > 1 else []
            
            for pos_str in positions_data:
                if pos_str:  # 忽略空字符串
                    print(f"单个订单数据: {pos_str}")  # 打印单个订单数据
                    pos_parts = pos_str.split(",")  # 使用逗号作为分隔符
                    positions.append(Position(
                        ticket=int(pos_parts[0]),
                        symbol=pos_parts[1],
                        type=int(pos_parts[2]),
                        lots=float(pos_parts[3]),
                        open_price=float(pos_parts[4]),
                        sl=float(pos_parts[5]),
                        tp=float(pos_parts[6]),
                        profit=float(pos_parts[7]),
                        magic=int(pos_parts[8]),  # 获取魔法数字
                        commission=float(pos_parts[9]),  # 获取手续费
                        swap=float(pos_parts[10])  # 获取隔夜利息
                    ))
            return positions
        else:
            raise Exception(f"获取持仓信息失败: {response}")
        
    def open_order(self, symbol: str, type: int, lots: float, sl: float, tp: float, magic: int = 0) -> str:
        """
        开仓
        :param symbol: 货币对
        :param type: 订单类型 (0=买入, 1=卖出)
        :param lots: 交易量
        :param sl: 止损价格
        :param tp: 止盈价格
        :param magic: 魔法数字
        :return: 响应消息
        """
        command = f"OPEN_ORDER|{symbol}|{type}|{lots}|{sl}|{tp}|{magic}"
        return self._send_command(command)
        
    def close_order(self, ticket: int, lots: Optional[float] = None) -> str:
        """
        平仓
        :param ticket: 订单号
        :param lots: 平仓手数，如果为None则平掉全部仓位
        :return: 响应消息
        """
        if lots is None:
            command = f"CLOSE_ORDER|{ticket}"
        else:
            command = f"CLOSE_ORDER|{ticket}|{lots}"
        return self._send_command(command)
        
    def modify_order(self, ticket: int, stop_loss: float, take_profit: float) -> str:
        """修改止损止盈"""
        response = self._send_command(f"MODIFY_ORDER|{ticket}|{stop_loss}|{take_profit}")
        return response
        
    def get_market_info(self, symbol: str) -> MarketInfo:
        """获取市场数据"""
        response = self._send_command(f"MARKET_INFO|{symbol}")
        parts = response.split("|")
        
        if parts[0] == "MARKET_INFO":
            return MarketInfo(
                symbol=parts[1],
                bid=float(parts[2]),
                ask=float(parts[3]),
                point=float(parts[4]),
                lot_size=float(parts[5])
            )
        else:
            raise Exception(f"获取市场数据失败: {response}")

    def _get_bars_from_mt4(self, symbol: str, timeframe: int, count: int) -> List[dict]:
        """直接从MT4获取K线数据
        Args:
            symbol: 交易对名称
            timeframe: K线周期（分钟）
            count: 请求的K线数量
        Returns:
            List[dict]: K线数据列表
        """
        response = self._send_command(f"GET_BARS|{symbol}|{timeframe}|{count}")
        parts = response.split("|", 1)
        
        if parts[0] == "BARS":
            bars = []
            bars_data = parts[1].split(";")
            for bar_str in bars_data:
                if bar_str:
                    bar_parts = bar_str.split("|")
                    if len(bar_parts) >= 7:
                        bars.append({
                            "time": int(bar_parts[0]),
                            "open": float(bar_parts[1]),
                            "high": float(bar_parts[2]),
                            "low": float(bar_parts[3]),
                            "close": float(bar_parts[4]),
                            "volume": float(bar_parts[5]),
                            "spread": int(bar_parts[6])
                        })
            return bars
        else:
            raise Exception(f"获取K线数据失败: {response}")

    def get_bars(self, symbol: str, timeframe: int, count: int = 100, 
                use_cache: bool = True, smart_update: bool = True) -> List[dict]:
        """获取K线数据，支持缓存和智能更新
        Args:
            symbol: 交易对名称
            timeframe: K线周期（分钟）
            count: 请求的K线数量
            use_cache: 是否使用缓存
            smart_update: 是否启用智能更新（基于时间差）
        Returns:
            List[dict]: K线数据列表
        """
        try:
            if not use_cache:
                # 不使用缓存，直接从MT4获取数据
                return self._get_bars_from_mt4(symbol, timeframe, count)
            
            # 从缓存加载数据
            cached_klines = self.kline_cache.load_cache(symbol, timeframe)
            last_time = self.kline_cache.get_last_kline_time(symbol, timeframe)
            
            if not smart_update:
                # 如果不启用智能更新，直接返回缓存数据
                return sorted(cached_klines, key=lambda x: x['time'], reverse=True)[:count]
            
            # 智能更新逻辑
            if last_time is None:
                # 如果没有缓存数据，获取请求数量的K线
                new_klines = self._get_bars_from_mt4(symbol, timeframe, count)
            else:
                # 检查最新K线的时间
                latest_bar_time = cached_klines[0]['time'] if cached_klines else None
                current_time = int(time.time())
                
                if latest_bar_time:
                    time_diff_hours = (current_time - latest_bar_time) / 3600
                    print(f"最新K线时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(latest_bar_time))}")
                    print(f"当前时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(current_time))}")
                    print(f"时间差: {time_diff_hours:.1f} 小时")
                    
                    # 如果时间差小于1小时，直接使用缓存数据
                    if time_diff_hours < 1:
                        print("时间差小于1小时，使用缓存数据")
                        return sorted(cached_klines, key=lambda x: x['time'], reverse=True)[:count]
                    
                    # 计算需要更新的K线数量
                    bars_to_update = min(int(time_diff_hours) + 1, 100)
                    print(f"需要更新 {bars_to_update} 根K线")
                    new_klines = self._get_bars_from_mt4(symbol, timeframe, bars_to_update)
                else:
                    new_klines = self._get_bars_from_mt4(symbol, timeframe, 10)  # 获取最新的10根K线
            
            # 合并新旧数据
            merged_klines = self.kline_cache.merge_klines(cached_klines, new_klines)
            
            # 清理旧数据，只保留最新的1000根K线
            cleaned_klines = self.kline_cache.clean_old_klines(merged_klines)
            
            # 保存到缓存
            self.kline_cache.save_cache(symbol, timeframe, cleaned_klines)
            
            # 返回请求数量的最新K线
            return sorted(cleaned_klines, key=lambda x: x['time'], reverse=True)[:count]
            
        except Exception as e:
            print(f"获取K线数据时出错: {str(e)}")
            import traceback
            traceback.print_exc()
            return []
        
    def subscribe_price(self, symbol: str) -> str:
        """订阅价格更新"""
        response = self._send_command(f"SUBSCRIBE_PRICE|{symbol}")
        return response
        
    def unsubscribe_price(self, symbol: str) -> str:
        """取消订阅价格更新"""
        response = self._send_command(f"UNSUBSCRIBE_PRICE|{symbol}")
        return response
        
    def get_price_update(self, timeout_ms: int = 1000) -> Optional[dict]:
        """
        获取价格更新
        :param timeout_ms: 超时时间（毫秒）
        :return: 价格更新信息，如果超时则返回None
        """
        try:
            self.sub_socket.RCVTIMEO = timeout_ms
            message = self.sub_socket.recv_string()
            parts = message.split("|")
            
            if parts[0] == "PRICE_UPDATE":
                return {
                    "symbol": parts[1],
                    "bid": float(parts[2]),
                    "ask": float(parts[3]),
                    "time": int(parts[4])
                }
            return None
        except zmq.error.Again:
            return None
            
    def close(self):
        """显式关闭连接"""
        try:
            if hasattr(self, 'socket'):
                self.socket.close()
            if hasattr(self, 'sub_socket'):
                self.sub_socket.close()
            if hasattr(self, 'context'):
                self.context.term()
        except Exception as e:
            print(f"关闭连接时发生错误: {str(e)}")
            
    def __del__(self):
        """析构函数"""
        self.close()

    def get_smart_bars(self, symbol: str, timeframe: int, count: int = 100) -> List[dict]:
        """
        智能获取K线数据，根据时间差决定是否需要更新
        Args:
            symbol: 交易对名称
            timeframe: K线周期（分钟）
            count: 请求的K线数量
        Returns:
            List[dict]: K线数据列表
        """
        try:
            # 先尝试从缓存获取数据
            bars = self.get_bars(symbol, timeframe, count, use_cache=True)
            if not bars:
                print("缓存中无数据，直接获取最新数据")
                bars = self.get_bars(symbol, timeframe, count, use_cache=False, smart_update=True)
                return bars
            
            # 检查最新K线的时间
            latest_bar_time = bars[0]['time']
            current_time = int(time.time())
            time_diff_hours = (current_time - latest_bar_time) / 3600
            
            print(f"最新K线时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(latest_bar_time))}")
            print(f"当前时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(current_time))}")
            print(f"时间差: {time_diff_hours:.1f} 小时")
            
            # 如果时间差小于1小时，直接使用缓存数据
            if time_diff_hours < 1:
                print("时间差小于1小时，使用缓存数据")
                return bars
            
            # 计算需要更新的K线数量
            bars_to_update = min(int(time_diff_hours) + 1, 100)  # 最多更新100根K线
            print(f"需要更新 {bars_to_update} 根K线")
            
            # 获取更新的K线数据并直接写入缓存
            updated_bars = self.get_bars(symbol, timeframe, bars_to_update, use_cache=False, smart_update=True)
            if not updated_bars:
                print("更新K线数据失败，使用缓存数据")
                return bars
            
            # 重新从缓存读取完整的K线数据
            print("从更新后的缓存中读取完整数据")
            merged_bars = self.get_bars(symbol, timeframe, count, use_cache=True)
            if not merged_bars:
                print("读取更新后的缓存失败，使用部分更新的数据")
                return updated_bars[:count]
            
            print(f"成功获取更新后的K线数据，共 {len(merged_bars)} 根")
            return merged_bars
            
        except Exception as e:
            print(f"获取K线数据时出错: {str(e)}")
            import traceback
            traceback.print_exc()
            return []

def main():
    """使用示例"""
    client = MT4Client()
    
    try:
        # 获取账户信息
        account = client.get_account_info()
        print(f"账户信息: {account}")
        
        # 获取EURUSD市场数据
        market = client.get_market_info("EURUSD")
        print(f"EURUSD行情: Bid={market.bid}, Ask={market.ask}")
        
        # 获取持仓信息
        positions = client.get_positions()
        print(f"当前持仓数量: {len(positions)}")
        for pos in positions:
            print(f"订单 {pos.ticket}: {pos.symbol} {pos.lots}手 盈亏={pos.profit}")
            
        # 获取K线数据
        bars = client.get_bars("EURUSD", 1, 10)  # 获取EURUSD的最近10根1分钟K线
        print("\nEURUSD最近10根K线:")
        for bar in bars:
            print(f"时间: {bar['time']}, 开: {bar['open']}, 高: {bar['high']}, 低: {bar['low']}, 收: {bar['close']}, 量: {bar['volume']}")
            
    except Exception as e:
        print(f"发生错误: {str(e)}")

if __name__ == "__main__":
    main() 